/*-------------------------------------------------------------------------
 *
 * system.c
 *	  支持 SYSTEM 表样本方法的例程
 *
 * 为了确保样本的重复性，选择给定元组的方式必须与历史无关；否则，同步扫描将打破重复性，更不用说诸如物理扩展或缩短关系等逻辑无关的维护。
 *
 * 为了实现这一点，我们通过将每个候选块号与活动种子进行哈希处理，然后选择它，如果哈希值小于由 BeginSampleScan 计算的选择概率的截止值。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/access/tablesample/system.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <math.h>

#include "access/relscan.h"
#include "access/tsmapi.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
#include "optimizer/optimizer.h"
#include "utils/builtins.h"


/* 私有状态 */
typedef struct
{
	uint64		cutoff;			/* 选择哈希值小于此的块 */
	uint32		seed;			/* 随机种子 */
	BlockNumber nextblock;		/* 下一个要考虑采样的块 */
	OffsetNumber lt;			/* 从当前块返回的最后一个元组 */
} SystemSamplerData;


static void fc_system_samplescangetsamplesize(PlannerInfo *fc_root,
										   RelOptInfo *fc_baserel,
										   List *fc_paramexprs,
										   BlockNumber *fc_pages,
										   double *fc_tuples);
static void fc_system_initsamplescan(SampleScanState *fc_node,
								  int fc_eflags);
static void fc_system_beginsamplescan(SampleScanState *fc_node,
								   Datum *fc_params,
								   int fc_nparams,
								   uint32 fc_seed);
static BlockNumber fc_system_nextsampleblock(SampleScanState *fc_node, BlockNumber fc_nblocks);
static OffsetNumber fc_system_nextsampletuple(SampleScanState *fc_node,
										   BlockNumber fc_blockno,
										   OffsetNumber fc_maxoffset);


/*
 * 为SYSTEM方法创建一个TsmRoutine描述符。
 */
Datum tsm_system_handler(PG_FUNCTION_ARGS)
{
	TsmRoutine *fc_tsm = makeNode(TsmRoutine);

	fc_tsm->parameterTypes = list_make1_oid(FLOAT4OID);
	fc_tsm->repeatable_across_queries = true;
	fc_tsm->repeatable_across_scans = true;
	fc_tsm->SampleScanGetSampleSize = fc_system_samplescangetsamplesize;
	fc_tsm->InitSampleScan = fc_system_initsamplescan;
	fc_tsm->BeginSampleScan = fc_system_beginsamplescan;
	fc_tsm->NextSampleBlock = fc_system_nextsampleblock;
	fc_tsm->NextSampleTuple = fc_system_nextsampletuple;
	fc_tsm->EndSampleScan = NULL;

	PG_RETURN_POINTER(fc_tsm);
}

/*
 * 样本大小估计。
 */
static void fc_system_samplescangetsamplesize(PlannerInfo *fc_root,
							   RelOptInfo *fc_baserel,
							   List *fc_paramexprs,
							   BlockNumber *fc_pages,
							   double *fc_tuples)
{
	Node	   *fc_pctnode;
	float4		fc_samplefract;

	/* 尝试提取样本百分比的估计值 */
	fc_pctnode = (Node *) linitial(fc_paramexprs);
	fc_pctnode = estimate_expression_value(fc_root, fc_pctnode);

	if (IsA(fc_pctnode, Const) &&
		!((Const *) fc_pctnode)->constisnull)
	{
		fc_samplefract = DatumGetFloat4(((Const *) fc_pctnode)->constvalue);
		if (fc_samplefract >= 0 && fc_samplefract <= 100 && !isnan(fc_samplefract))
			fc_samplefract /= 100.0f;
		else
		{
			/* 如果值无效，使用默认的samplefract */
			fc_samplefract = 0.1f;
		}
	}
	else
	{
		/* 如果没有获得非空的Const，则使用默认的samplefract */
		fc_samplefract = 0.1f;
	}

	/* 我们将访问部分页面... */
	*fc_pages = clamp_row_est(fc_baserel->pages * fc_samplefract);

	/* ... 并希望从中获取具有代表性的元组数量 */
	*fc_tuples = clamp_row_est(fc_baserel->tuples * fc_samplefract);
}

/*
 * 在执行器设置期间初始化。
 */
static void fc_system_initsamplescan(SampleScanState *fc_node, int fc_eflags)
{
	fc_node->tsm_state = palloc0(sizeof(SystemSamplerData));
}

/*
 * 检查参数并准备进行样本扫描。
 */
static void fc_system_beginsamplescan(SampleScanState *fc_node,
					   Datum *fc_params,
					   int fc_nparams,
					   uint32 fc_seed)
{
	SystemSamplerData *fc_sampler = (SystemSamplerData *) fc_node->tsm_state;
	double		fc_percent = DatumGetFloat4(fc_params[0]);
	double		fc_dcutoff;

	if (fc_percent < 0 || fc_percent > 100 || isnan(fc_percent))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLESAMPLE_ARGUMENT),
				 errmsg("sample percentage must be between 0 and 100")));

	/*
	 * 截止值是样本概率乘以(PG_UINT32_MAX + 1)；我们必须
	 * 将其存储为uint64，当然。请注意，在零或一的概率极限处，这提供了严格
	 * 正确的行为。
	 */
	fc_dcutoff = rint(((double) PG_UINT32_MAX + 1) * fc_percent / 100);
	fc_sampler->cutoff = (uint64) fc_dcutoff;
	fc_sampler->seed = fc_seed;
	fc_sampler->nextblock = 0;
	fc_sampler->lt = InvalidOffsetNumber;

	/*
	 * 批量读取缓冲区访问策略在扫描非常小的表的一部分时
	 * 可能是有意义的。这里的1%截止值是个估计。我们应该使用
	 * 页面模式可见性检查，因为我们扫描每个选定页面上的所有
	 * 元组。
	 */
	fc_node->use_bulkread = (fc_percent >= 1);
	fc_node->use_pagemode = true;
}

/*
 * 选择下一个要采样的块。
 */
static BlockNumber fc_system_nextsampleblock(SampleScanState *fc_node, BlockNumber fc_nblocks)
{
	SystemSamplerData *fc_sampler = (SystemSamplerData *) fc_node->tsm_state;
	BlockNumber fc_nextblock = fc_sampler->nextblock;
	uint32		fc_hashinput[2];

	/*
	 * 我们通过对包含块号和种子的2个uint32数组应用hash_any来计算哈希。
	 * 这在设置时是高效的，并且在当前hash_any实现下，提供了
	 * 与机器无关的结果，这是回归测试的一个不错属性。
	 *
	 * 这些哈希输入中的字在整个块中是相同的：
	 */
	fc_hashinput[1] = fc_sampler->seed;

	/*
	 * 循环遍历块号，直到找到合适的块或达到关系的末尾。
	 */
	for (; fc_nextblock < fc_nblocks; fc_nextblock++)
	{
		uint32		fc_hash;

		fc_hashinput[0] = fc_nextblock;

		fc_hash = DatumGetUInt32(hash_any((const unsigned char *) fc_hashinput,
									   (int) sizeof(fc_hashinput)));
		if (fc_hash < fc_sampler->cutoff)
			break;
	}

	if (fc_nextblock < fc_nblocks)
	{
		/* 找到一个合适的块；记住我们下次应该从哪里开始 */
		fc_sampler->nextblock = fc_nextblock + 1;
		return fc_nextblock;
	}

	/* 完成，但为了安全起见，我们将nextblock重置为0。 */
	fc_sampler->nextblock = 0;
	return InvalidBlockNumber;
}

/*
 * 在当前块中选择下一个被采样的元组。
 *
 * 在块采样中，我们只想采样每个选定块中的所有元组。
 *
 * 在这里返回一个偏移量而不知道元组是否可见（甚至是否存在）是可以的；
 * nodeSamplescan.c将处理这个问题。
 *
 * 当我们到达块的末尾时，返回InvalidOffsetNumber，指示
 * SampleScan转到下一个块。
 */
static OffsetNumber fc_system_nextsampletuple(SampleScanState *fc_node,
					   BlockNumber fc_blockno,
					   OffsetNumber fc_maxoffset)
{
	SystemSamplerData *fc_sampler = (SystemSamplerData *) fc_node->tsm_state;
	OffsetNumber fc_tupoffset = fc_sampler->lt;

	/* 向页面上的下一个可能偏移量推进 */
	if (fc_tupoffset == InvalidOffsetNumber)
		fc_tupoffset = FirstOffsetNumber;
	else
		fc_tupoffset++;

	/* 完成？ */
	if (fc_tupoffset > fc_maxoffset)
		fc_tupoffset = InvalidOffsetNumber;

	fc_sampler->lt = fc_tupoffset;

	return fc_tupoffset;
}
